/* ----------------------------------------------------------------------------
* File: game_controller.cpp
*
* Desc: Game controller for quiz functionality
*
* Created: 06/05/2011, by Alexey Korshunov
* ---------------------------------------------------------------------------- */
#include "game_controller.h"
#include "utils.h"
#include <time.h>

#define MAX_LIMIT_PROMPTS			3
#define FUN_POINTS					5
#define GAME_EVENTS					5

#define QUESTION_MIN_TIMEOUT		10

namespace QuizCore
{
	GameController::GameController(GameEventHandler* handler, const ControllerParams& params)
		throw(Common::Exception)
		: m_EventHandler(*handler)
		, m_CountQuestions(0)
		, m_CurNumQuestion(0)
		, m_CountPrompts(0)
		, m_PromptEvent(NULL)
		, m_AnswerTimeoutEvent(NULL)
		, m_PushQueueEvent(NULL)
		, m_NextQuestionEvent(NULL)
		, m_GameStarted(false)
	{
		using namespace Common;
		m_Logger = Common::CreateDebugLogger();
		if (!m_Logger.get())
			throw Common::Exception("GameController: ERROR - Unable to create debug logger");

		m_PromptEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
		if (m_PromptEvent == NULL)
			throw Common::Exception("GameController: ERROR - Unable to create prompt event");

		m_AnswerTimeoutEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
		if (m_AnswerTimeoutEvent == NULL)
			throw Common::Exception("GameController: ERROR - Unable to create answer timeout event");

		m_NextQuestionEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
		if (m_NextQuestionEvent == NULL)
			throw Common::Exception("GameController: ERROR - Unable to create next question event");

		CheckParams(params);
		m_Params = params;
		m_PushQueueEvent = m_Params.Queue->GetPushEvent();

		DictionaryInfo info;
		m_Params.DictManager->GetDictionaryInfo(info);
		m_CountQuestions = info.CountQuestions;

		InitializeCriticalSection(&m_csAnswerLock);
	}

	GameController::~GameController()
	{
		CloseHandle(m_PromptEvent);
		CloseHandle(m_AnswerTimeoutEvent);
		DeleteCriticalSection(&m_csAnswerLock);
	}

	Common::Error GameController::Start()
	{
		return StartThread() ? Common::SKYPE_SUCCESS : Common::SKYPE_ERROR;
	}

	Common::Error GameController::Stop()
	{
		StopThread();
		return Common::SKYPE_SUCCESS;
	}

	Common::Error GameController::GeneratePrompt()
	{
		if (!m_GameStarted)
			return Common::SKYPE_ERROR;

		::SetEvent(m_PromptEvent);
		return Common::SKYPE_SUCCESS;
	}

	Common::Error GameController::NextQuestion()
	{
		if (!m_GameStarted)
			return Common::SKYPE_ERROR;

		::SetEvent(m_NextQuestionEvent);
		return Common::SKYPE_SUCCESS;
	}

	Common::Error GameController::Run()
	{
		HANDLE Events[GAME_EVENTS];
		Events[0] = m_AnswerTimeoutEvent;
		Events[1] = m_PromptEvent;
		Events[2] = m_hEventUserAbort;
		Events[3] = m_PushQueueEvent;
		Events[4] = m_NextQuestionEvent;

		m_Logger->Print("GameController: Start");

		srand((unsigned)time(NULL));
		std::auto_ptr<Common::Timer> AnswerTimer = CreateQuestionTimer();
		std::auto_ptr<Common::Timer> PromptTimer;
		if (m_Params.AutoPrompts)
			PromptTimer = CreatePromptTimer();

		std::string question;
		m_GameStarted = true;
		m_CurNumQuestion = 0;
		while(!QueryExitStatus(0))
		{
			// Delay before new question
			if (m_CurNumQuestion > 0 && m_Params.QuestionDelay)
			{
				QuestionDelayNotifyParams params;
				params.delay = m_Params.QuestionDelay / 1000;
				m_EventHandler.QuestionDelayNotify(params);
				Sleep(m_Params.QuestionDelay);
			}

			// Process new question
			ProcessNewQuestion(question);
			QuestionNotifyParams questionParams;
			questionParams.question = question;
			questionParams.answer_symbols = m_Answer.length();
			m_EventHandler.QuestionNotify(questionParams);
			AnswerTimer->Start();
			if (m_Params.AutoPrompts)
				PromptTimer->Start();
			m_CountPrompts = 0;

			bool stop_question = false;
			while(!stop_question)
			{
				DWORD dw = WaitForMultipleObjects(GAME_EVENTS, (const HANDLE*)Events, FALSE, INFINITE);
				switch(dw)
				{
				case WAIT_OBJECT_0:		// answer timeout
					{
						m_Logger->Print("GameController: answer timeout event");
						::ResetEvent(m_AnswerTimeoutEvent);
						TimeoutNotifyParams timeoutParams;
						timeoutParams.answer = m_Answer;
						m_EventHandler.TimeoutNotify(timeoutParams);
						stop_question = true;
					}
					break;

				case WAIT_OBJECT_0 + 1:	// prompt
					{
						m_Logger->Print("GameController: prompt event");
						::ResetEvent(m_PromptEvent);
						if (m_CountPrompts < MAX_LIMIT_PROMPTS)
						{
							std::string prompt;
							CreatePrompt(prompt);
							PromptNotifyParams promptParams;
							promptParams.prompt = prompt;
							m_EventHandler.PromptNotify(promptParams);
						}
					}
					break;

				case WAIT_OBJECT_0 + 2:	// user stop
					{
						m_Logger->Print("GameController: user stop event");
						stop_question = true;
					}
					break;

				case WAIT_OBJECT_0 + 3:
					{
						m_Logger->Print("GameController: push queue event");
						::ResetEvent(m_PushQueueEvent);
						MsgInfo info;
						if (CheckQueue(info))
						{
							m_Params.ProfileManager->GivePoints(info.user, FUN_POINTS - m_CountPrompts);
							CorrectAnswerNotifyParams answerParams;
							answerParams.user = info.user;
							answerParams.points = FUN_POINTS - m_CountPrompts;
							m_EventHandler.CorrectAnswerNotify(answerParams);
							stop_question = true;
						}
					}
					break;

				case WAIT_OBJECT_0 + 4:	// next question
					{
						m_Logger->Print("GameController: next question event");
						::ResetEvent(m_NextQuestionEvent);
						NextQuestionNotifyParams params;
						params.answer = m_Answer;
						m_EventHandler.NextQuestionNotify(params);
						stop_question = true;
					}
					break;

				default:
					break;
				}

				// Stop timers
				if (stop_question)
				{
					AnswerTimer->Stop();
					if (m_Params.AutoPrompts)
						PromptTimer->Stop();
				}
			}
		}

		m_GameStarted = false;
		m_Logger->Print("GameController: Stop");
		return Common::SKYPE_SUCCESS;
	}

	Common::Error GameController::CheckParams(const ControllerParams& params)
	{
		using namespace Common;
		if (params.DictManager == NULL)
			throw Exception("GameController: ERROR - QuizDictionary object is null");
		if (params.ProfileManager == NULL)
			throw Exception("GameController: ERROR - UserProfile object is null");
		if (params.Queue == NULL)
			throw Exception("GameController: ERROR - MessageQueue object is null");

		if (params.QuestionTimeout < QUESTION_MIN_TIMEOUT)
		{
			std::string msg;
			sprintf((char*)msg.c_str(), "GameController: ERROR - Incorrect QuestionTimeout parameter. Min value is %d", QUESTION_MIN_TIMEOUT);
			throw Exception(msg);
		}
		return SKYPE_SUCCESS;
	}

	std::auto_ptr<Common::Timer> GameController::CreatePromptTimer()
	{
		using namespace Common;
		TimerParams params;
		params.TimeOut = m_Params.PromptTime;
		params.TimeCallback = &PromptTimeoutCallback;
		params.Parent = this;
		std::auto_ptr<Timer> timer(CreateTimer(params));
		if (!timer.get())
			throw Exception("GameController: ERROR - Unable to create prompt timer");
		return timer;
	}

	std::auto_ptr<Common::Timer> GameController::CreateQuestionTimer()
	{
		using namespace Common;
		TimerParams params;
		params.TimeOut = m_Params.QuestionTimeout;
		params.TimeCallback = &AnswerTimeoutCallback;
		params.Parent = this;
		std::auto_ptr<Timer> timer(CreateTimer(params));
		if (!timer.get())
			throw Exception("GameController: ERROR - Unable to create question timer");
		return timer;
	}

	void GameController::ProcessNewQuestion(std::string& question)
	{
		int RangeMin = 0;
		int RangeMax = m_CountQuestions - 1;

		unsigned int Id = ((double)rand() / (double)RAND_MAX) * RangeMax + RangeMin;
		m_Params.DictManager->SetCurNumberQuestion(Id);

		question = m_Params.DictManager->GetQuestion();
		++m_CurNumQuestion;

		Common::Utils::LockGuard lock(m_csAnswerLock);
		m_Answer = m_Params.DictManager->GetAnswer();
	}

	void GameController::CreatePrompt(std::string& prompt)
	{
		Common::Utils::LockGuard lock(m_csAnswerLock);
		size_t num = m_CountPrompts + 1;
		prompt.clear();
		prompt.append(m_Answer.c_str(), num);
		++m_CountPrompts;
	}

	bool GameController::CheckQueue(MsgInfo& info)
	{
		MsgInfo msgInfo;
		size_t count = m_Params.Queue->size();
		while(count > 0)
		{
			msgInfo = m_Params.Queue->front();
			m_Logger->Print("GameController: Check new answer - %s", msgInfo.msg.c_str());

			// if message and answer are identical then call notify function
			if (msgInfo.msg == m_Answer)
			{
				m_Logger->Print("GameController: GONGRATULATIONS - Answer is correct!!");
				info = msgInfo;
				m_Params.Queue->clear();
				return true;
			}
			m_Params.Queue->pop();
			--count;
		}
		return false;
	}

	void GameController::PromptTimeoutCallback(void* param)
	{
		GameController* controller = reinterpret_cast<GameController*>(param);
		if (controller)
		{
			::SetEvent(controller->m_PromptEvent);
		}
	}

	void GameController::AnswerTimeoutCallback(void* param)
	{
		GameController* controller = reinterpret_cast<GameController*>(param);
		if (controller)
		{
			::SetEvent(controller->m_AnswerTimeoutEvent);
		}
	}
}

